﻿using System;
using System.ComponentModel.Composition;
using ZOIL.Framework.Presentation;
using Bespoke.Common.Osc;
using System.Net;
using System.Windows;

namespace ZOIL.Library.Behaviors
{
    [Export(typeof(SynchronizeViewportPluginBehavior))]
    public class SynchronizeViewportOscBehavior : SynchronizeViewportPluginBehavior
    {
        #region private members

        private OscServer _server;
        private string _senderIDNotInDispatcherMode = null;

        private const string ViewportAddress = "/viewport";
        private readonly IPAddress _senderIP = IPAddress.Parse("0.0.0.0");
        private readonly IPAddress _targetIP = IPAddress.Parse("224.5.6.7");
        private const short Port = 4411;

        #endregion

        #region overrides

        protected override void OnViewportChanged(ZInformationLandscape landscape)
        {
            if (Type == CommunicationType.Sender || Type == CommunicationType.Both)
                SendViewportCommand(landscape.Viewport0);
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            PrepareServer();
            ResolveSenderID();
        }

        protected override void OnTypeChanged(CommunicationType oldType, CommunicationType newType)
        {
            base.OnTypeChanged(oldType, newType);

            PrepareServer();
        }

        protected override void OnSenderIDChanged(string oldSenderID, string newSenderID)
        {
            base.OnSenderIDChanged(oldSenderID, newSenderID);

            ResolveSenderID();
        }

        void ServerBundleReceived(object sender, OscBundleReceivedEventArgs e)
        {
            foreach (var message in e.Bundle.Messages)
            {
                HandleMessage(message);
            }
        }

        void ServerMessageReceived(object sender, OscMessageReceivedEventArgs e)
        {
            HandleMessage(e.Message);
        }

        #endregion

        #region private methods

        private void PrepareServer()
        {
            if (_server != null)
            {
                _server.Stop();
                _server.BundleReceived -= ServerBundleReceived;
                _server.MessageReceived -= ServerMessageReceived;
                _server = null;
            }

            if (Type == CommunicationType.Receiver || Type == CommunicationType.Both)
            {
                _server = new OscServer(_targetIP, Port);

                // Register methods
                _server.RegisterMethod(ViewportAddress);

                _server.BundleReceived += ServerBundleReceived;
                _server.MessageReceived += ServerMessageReceived;
                _server.Start();
            }
        }

        private void ResolveSenderID()
        {
            _senderIDNotInDispatcherMode = (string)Dispatcher.Invoke(new Func<string>(() => SenderID));
        }

        private void SendViewportCommand(Rect viewport)
        {
            var msg = new OscMessage(new IPEndPoint(_senderIP, Port), ViewportAddress);
            msg.Append(!string.IsNullOrEmpty(SenderID) ? SenderID : "anonymous");
            msg.Append(viewport.Left);
            msg.Append(viewport.Top);
            msg.Append(viewport.Width);
            msg.Append(viewport.Height);

            msg.Send(new IPEndPoint(_targetIP, Port));
        }

        private void HandleMessage(OscPacket message)
        {
            var messageSenderID = message.At<string>(0);

            if (_senderIDNotInDispatcherMode.Equals(messageSenderID)) return;

            switch (message.Address)
            {
                case ViewportAddress:
                    HandleViewportMessage(message);
                    break;
            }
        }

        private void HandleViewportMessage(OscPacket message)
        {
            var left = message.At<double>(1);
            var top = message.At<double>(2);
            var width = message.At<double>(3);
            var height = message.At<double>(4);

            var rect = new Rect(left, top, width, height);

            Dispatcher.BeginInvoke((Action) (() => ParentBehavior.RaiseViewportReceivedEvent(new Viewport(rect))));
        }

        #endregion
    }
}
